#pragma once

#include "Sock.hpp"
#include <memory>
#include <unordered_map>
#include <sys/select.h>

const int fds_num_max = sizeof(fd_set) * 8;
const int defaultfd = -1;

class Selectserver
{
public:
    Selectserver(uint16_t port)
        : _sock_ptr(new Sock(port))
    {
        for (int i = 0; i < fds_num_max; i++)
        {
            _fds_array[i] = defaultfd;
        }
    }
    void AddToArray(int fd)
    {
        int pos = 0;
        for (; pos < fds_num_max && _fds_array[pos] != defaultfd; pos++)
            ;

        if (pos == fds_num_max)
        {
            // select已经到达监听极限了,不能再添加要进行监听的文件了

            // 1. 关闭文件
            // 2. 打印日志

            close(fd);
            Log(Warning) << "select is full";
        }
        else
        {
            // 1. 有位置直接进行添加
            _fds_array[pos] = fd;
            Log(Info) << "add a new fd : " << fd;
        }
    }
    void Init()
    {
        // 1. 创建套接字
        // 2. 绑定
        // 3. 设置监听
        // 4. 将套接字描述符加入到_fds_array数组中

        _sock_ptr->Socket();
        _sock_ptr->Bind();
        _sock_ptr->Listen();
        AddToArray(_sock_ptr->Get_fd());
    }

    std::pair<fd_set , int> Get_readfds()
    {
        // 1. 对位图进行初始化
        // 2. 循环遍历_fds_array数组,将要进行等待的文件描述符添加到位图中

        int max_num = 0;
        fd_set readfds;
        FD_ZERO(&readfds);
        for (int i = 0; i < fds_num_max; i++)
        {
            if (_fds_array[i] == -1)
                continue;
            FD_SET(_fds_array[i], &readfds);
            max_num = std::max(max_num , _fds_array[i]);
        }
        return std::make_pair(readfds , max_num);
    }

    // 是套接字就绪
    void Sockfd_Ready()
    {
        // 1. 将套接字中建立好的连接拿上来
        // 2. 将拿上来的文件描述符加入到_fds_array中,等到客户端发送消息过来

        int fd = _sock_ptr->Accept();
        AddToArray(fd);
    }

    // fd表示文件描述符 , i表示在数组中的位置
    void Normalfd_Ready(int fd , int i)
    {
        // 1. 读取文件描述符中的数据
        // 2. 将数据简单处理后,进行返回（此处假设TCP接收的报文是完整的）

        char inbuffer[1024];
        int n = read(fd , inbuffer , sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::string ret = "server got a message : ";
            ret += inbuffer;

            write(fd , ret.c_str() , ret.size());
        }
        else  if(n == 0)
        {
            // 对方已经关闭文件

            // 1. 将在_fds_array中的对应位置设为-1表示已经被移除了,不需要再进行等待
            // 2. 关闭文件描述符
            _fds_array[i] = defaultfd;
            close(fd);
        }
        else
        {
            // 出错了
            Log(Error) << "read fail";
        }
    }

    void Dispather(fd_set* fdreads)
    {
        int listensock = _sock_ptr->Get_fd();
        for(int i = 0 ; i < fds_num_max ; i++)
        {
            if(_fds_array[i] == defaultfd || !FD_ISSET(_fds_array[i] , fdreads)) continue;
            
            if(_fds_array[i] == listensock) 
            {
                Sockfd_Ready();
            }
            else
            {
                Normalfd_Ready(_fds_array[i] , i);
            }
        }
    }

    void Run()
    {
        while (true)
        {
            auto [fdreads , max_num] = Get_readfds();
            int n = select(max_num + 1 , &fdreads , nullptr , nullptr , nullptr);
            if(n > 0)
            {
                // 有事件就绪, 进行任务的派发
                Dispather(&fdreads);
            }
            else if(n == 0)
            {
                Log(Info) << "no file";
            }
            else
            {
                Log(Error) << "select fail";
            }
        }
    }

private:
    std::shared_ptr<Sock> _sock_ptr;
    int _fds_array[fds_num_max]; // 该数组用来存储select要进行等待的文件描述符,初始值为-1
};